﻿#region License
// <copyright file="RepositoryTemplate.cs" company="Kopigi - Marc Plessis">
// Copyright © Kopigi 2011
// </copyright>
// ****************************************************************************
// <author>Marc Plessis</author>
// <email>marc.plessis@kopigi.fr</email>
// <date>03/04/2011</date>
// <project>Biwo</project>
// <web>http://www.kopigi.fr/biwo</web>
// <license>
// See license.txt in this solution or http://www.microsoft.com/opensource/licenses.mspx
// </license>
#endregion

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using SQLServer.Repository.DAL.RepositoryDao.Extensions.LinqKit;
using SQLServer.Repository.POCO.Generated;

namespace SQLServer.Repository.DAL.RepositoryDao
{
    /// <summary>
    /// A generic repository for working with data in the database
    /// </summary>
    /// <typeparam name="T">A POCO that represents an Entity Framework entity</typeparam>
    public class Repository<T> : IRepository<T> where T : class
    {
    	#region properties
        	
        /// <summary>
        /// The context object for the database
        /// </summary>
        private ObjectContext _context;
    
        /// <summary>
        /// The IObjectSet that represents the current entity.
        /// </summary>
        private readonly IObjectSet<T> _objectSet;
    	
    	#endregion
    	
    	#region constructors
        /// <summary>
        /// Initializes a new instance of the DataRepository class
        /// </summary>
        public Repository()
            : this(new BiwoSampleContainer())
        {}
    
        /// <summary>
        /// Initializes a new instance of the DataRepository class
        /// </summary>
        /// <param name="context">The Entity Framework ObjectContext</param>
        /// <param name="proxyAndLazyLoadingEnabled">Set ProxyCreationEnabled and LazyLoadingEnabled, default as true</param>
        public Repository(ObjectContext context, bool proxyAndLazyLoadingEnabled = true)
        {
            _context = context;
    		_context.ContextOptions.ProxyCreationEnabled = proxyAndLazyLoadingEnabled;
    		_context.ContextOptions.LazyLoadingEnabled = proxyAndLazyLoadingEnabled;
            _objectSet = _context.CreateObjectSet<T>();
        }
    	#endregion
    	
    	#region public
    	
    	/// <summary>
        /// Adds the specified entity
        /// </summary>
        /// <param name="entity">Entity to add</param>
    	/// <param name="prefixPrimaryKey">Object prefix for value of PrimaryKey</param>
        /// <exception cref="ArgumentNullException"> if <paramref name="entity"/> is null</exception>
        public void Add(T entity, object prefixPrimaryKey = null)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            
            if (this.IsNew(entity))
            {
                Dictionary<PrimaryKeySequenceAttribute, PropertyInfo> propertyInfos;
                PrimaryKeySequenceAttribute.GetPrimariesKeySequenceAttribute<T>(entity, out propertyInfos);
                foreach (KeyValuePair<PrimaryKeySequenceAttribute, PropertyInfo> propertyInfo in propertyInfos)
                {
                    var _id = PrimaryKeySequenceAttribute.GetId(propertyInfo.Key, propertyInfo.Value, _objectSet, entity, _context, prefixPrimaryKey);
                    if (_id != null)
                    {
                        propertyInfo.Value.SetValue(entity, Convert.ChangeType(_id, propertyInfo.Value.PropertyType), null);
                    }   
                }
            }
            _objectSet.AddObject(entity);
        }
    
        /// <summary>
        /// Attaches the specified entity
        /// </summary>
        /// <param name="entity">Entity to attach</param>
        public void Attach(T entity)
        {
            _objectSet.Attach(entity);
        }
    	
    	/// <summary>
        /// Deletes the specified entitiy
        /// </summary>
        /// <param name="entity">Entity to delete</param>
        /// <exception cref="ArgumentNullException"> if <paramref name="entity"/> is null</exception>
        public void Delete(T entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
    
            _objectSet.DeleteObject(entity);
        }
    
        /// <summary>
        /// Deletes records matching the specified criteria
        /// </summary>
        /// <param name="predicate">Criteria to match on</param>
        public void Delete(Expression<Func<T, bool>> predicate)
        {
            IEnumerable<T> records = from x in _objectSet.Where<T>(predicate) select x;
            foreach (T record in records)
            {
                _objectSet.DeleteObject(record);
            }
        }
    	
        /// <summary>
        /// Deletes all records
        /// </summary>
        public void DeleteAll()
        {
            foreach(T record in _objectSet)
            {
                _objectSet.DeleteObject(record);
            }
        }
    
    	/// <summary>
        /// Gets all records as an IQueryable
        /// </summary>
        /// <returns>An IQueryable object containing the results of the query</returns>
        public IQueryable<T> Fetch()
        {
            return _objectSet;
        }
    
    	/// <summary>
        /// The first record matching the specified criteria
        /// </summary>
        /// <param name="predicate">Criteria to match on</param>
        /// <returns>A single record containing the first record matching the specified criteria</returns>
        public T First(Expression<Func<T, bool>> predicate)
        {
            return _objectSet.First<T>(predicate);
        }
    	
    	/// <summary>
        /// Gets all records as an IEnumberable
        /// </summary>
        /// <returns>An IQueryable object containing the results of the query</returns>
        public IQueryable<T> GetAll()
        {
            return _objectSet;
        }
    	
    	/// <summary>
        /// Get entity by id
        /// </summary>
        /// <param name="id">id to load entity</param>
    	/// <param name="searchInEntitiesUnSaving">Include search into entities not saving yet</param>
        /// <returns>T entity</returns>
        public T GetById(object id, bool searchInEntitiesUnSaving = false)
        {
    		return (T)_context.GetObjectByKey(new EntityKey(_context.DefaultContainerName + "." + typeof(T).Name, PrimaryKeyAttribute.GetPrimariesKeyInfo<T>().First().Name, Convert.ChangeType(id,PrimaryKeyAttribute.GetPrimariesKeyInfo<T>().First().PropertyType)));
        }
    	
    	///<summary>
    	/// Get state of an entity
    	///</summary>
    	/// <param name="entity">Entity to search</param>
        /// <returns>EntityState</returns>
     	public EntityState GetEntityState(T entity)
    	{
    		ObjectStateEntry objectStateEntry = null;
        	_context.ObjectStateManager.TryGetObjectStateEntry(entity, out objectStateEntry);
        	return objectStateEntry != null ? objectStateEntry.State : EntityState.Unchanged;
    	}
    	
    	///<summary>
    	/// Get entities filter by an EntityState
    	///</summary>
    	/// <param name="entity">EntityState to filter</param>
        /// <returns>List of entities</returns>
        public IEnumerable<ObjectStateEntry> GetObjectStateEntry(EntityState stateFilter)
    	{
    		return _context.ObjectStateManager.GetObjectStateEntries(stateFilter);
    	}
    	
        /// <summary>
        /// Check if entity is new
        /// </summary>
        /// <param name="entity">entity to check</param>
        /// <returns>boolean</returns>
        public bool IsNew(T entity)
        {
            ObjectStateEntry objectStateEntry = null;
            if(_context.ObjectStateManager.TryGetObjectStateEntry(entity,out objectStateEntry))
            {
                return objectStateEntry.State == EntityState.Added;
            }
            return true;
        }
    
    	/// <summary>
        /// Save entity
        /// </summary>
        /// <param name="entity">Entity to save</param>
    	/// <param name="prefixPrimaryKey">Object prefix for value of PrimaryKey</param>
        public T Save(T entity, object prefixPrimaryKey = null)
        {
            if (this.IsNew(entity))
            {
                Add(entity, prefixPrimaryKey);
            }
            else
            {
                if (_context.ObjectStateManager.GetObjectStateEntry(entity).State != EntityState.Detached)
                {
                    _context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
                }
                else
                {
                    Attach(entity);
                }
            }
            _context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
            return entity;
        }
    
        /// <summary>
        /// Saves all context changes
        /// </summary>
        public int SaveChanges()
        {
            return _context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
        }
    	
        /// <summary>
        /// Gets a single record by the specified criteria (usually the unique identifier)
        /// </summary>
        /// <param name="predicate">Criteria to match on</param>
        /// <returns>A single record that matches the specified criteria</returns>
        public T Single(Expression<Func<T, bool>> predicate)
        {
    		if (_objectSet.Count() == 0) return null;
            return _objectSet.Single<T>(predicate);
        }
    	
    	/// <summary>
        /// Find records with the specified criteria
        /// </summary>
        /// <param name="predicate">Criteria to match on</param>
        /// <returns>A collection containing the results of the query</returns>
        public IQueryable<T> Where(Expression<Func<T, bool>> predicate)
        {
            return _objectSet.Where<T>(predicate).AsExpandable();
        }
    	
        /// <summary>
        /// Releases all resources used by the context
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    	#endregion
    
        #region Private
    
        /// <summary>
        /// Releases all resources used by the WarrantManagement.DataExtract.Dal.ReportDataBase
        /// </summary>
        /// <param name="disposing">A boolean value indicating whether or not to dispose managed resources</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_context != null)
                {
                    _context.Dispose();
                    _context = null;
                }
            }
        }
    
        #endregion
    }
}
